<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    /*
      要点: 副作用函数 拦截对象操作 访问时应保存副作用函数 修改时应执行副作用函数

      1. 拦截操作: proxy
      2. 桶存储副作用函数: 桶是用来存储副作用函数的 在访问属性时将副作用函数放入桶中 在修改属性时将副作用函数执行
      3. 属性与副作用函数保持对应性: 对象与属性对应 属性与副作用函数对应 多个属性可以对应多个副作用函数 (对应关系能对应到vue2的哪些)
      4. 分支切换: 不被使用到的属性 不应该触发与其对应的副作函数的执行
      5. 副作用函数嵌套: 全局保存的副作用函数永远会指向内层嵌套的副作用函数 需要模拟一个数组让全局指向最外层的副作用函数 避免副作用函数嵌套
      6. 避免递归调用: 如果当前属性的副作用函数正在执行 则不应该被添加到依赖列表中 避免递归调用
      7. 调度执行 => 副作用函数的触发时机可以被控制: 选择使用配置项 在副作用函数执行时 将副作用函数传递出来 以自定义的方式执行副作用函数
      8. 避免重复执行: 使用Promise.resolve 
      9. 懒执行: 使用配置项进行判断 如果是懒执行 就不执行副作用函数 将其返回
      10. 计算属性: 将自定义的副作用函数 通过effect进行响应式的处理 将属性变为响应式属性 然后的到副作用函数的计算结果 (读取时才会真正执行这个副作用函数得到结果)
      11. 监视属性: 副作用函数添加依赖追踪 调度器执行传递的回调函数
    */
    let tempe2 , temp1
    const data = { name: 'jack', ok: true, a: 1, b: 2 }
    let activeEffect
    const bucket = new WeakMap()
    let effectStack = []

    const jobQueue = new Set()
    const p = Promise.resolve()
    let isFlushing = false 

    const obj = new Proxy(data, {
      get(target, key) {
        track(target, key)
        return target[key]
      },
      set(target, key, value) {
        target[key] = value
        trigger(target, key)
        return true
      }
    })

    function track(target, key){
      // console.log(123);
      // 如果没有 activeEffect 说明没有副作用函数 无需关心
      if (!activeEffect) return target[key]

      // 获取对象与属性的对应关系 如果没有则创建
      let depsMap = bucket.get(target)
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()))
      }

      // 获取属性对应的副作用函数 如果没有则创建
      let deps = depsMap.get(key)
      if (!deps) {
        depsMap.set(key, (deps = new Set()))
      }

      // 将当前 activeEffect 加入副作用函数的依赖列表
      deps.add(activeEffect)

      // 将属性与副作用函数对应的关系保存在 activeEffect 的 deps 属性中
      activeEffect.deps.push(deps)

      // console.log(activeEffect.deps);
      
    }

    function trigger(target, key) {
      // 根据target获取对应的属性与副作用函数的对应关系
      const depsMap = bucket.get(target)
      if (!depsMap) return true

      // 根据key获取属性对应的副作用函数
      const effects = depsMap.get(key)

      // 通过新的set避免死循环问题
      const effectsToRun = new Set(effects)

      effects && effects.forEach(effectFn => {
        // 正在添加的副作用函数与正在执行的副作用函数不是同一个 才会被添加 避免递归调用
        if(effectFn !== activeEffect ){
          effectsToRun.add(effectFn)
        }
      })

      effectsToRun.forEach(effectFn => {
        if(effectFn.options.scheduler){
          // 将副作用函数交给用户 让其自定义执行
          effectFn.options.scheduler(effectFn)
        } else {
          effectFn()
        }
      })
      // effect && effect.forEach(fn => fn())
    }

    function effect(fn, options = {}) {
      const effectFn = () => {
        // 清空副作用函数的依赖
        cleanup(effectFn)
        // 这个变量保存副作用函数
        activeEffect = effectFn
        // 让正在执行的副作用函数入栈
        effectStack.push(effectFn)
        // 让原有的副作用函数执行
        const res = fn()
        // 副作用函数执行完毕后出栈
        effectStack.pop()
        // 恢复 activeEffect
        activeEffect = effectStack[effectStack.length - 1]
        return res
      }
      // 配置项挂载到副作用函数上
      effectFn.options = options
      // 保存副作用函数的依赖列表
      effectFn.deps = []
      // 让副作用函数实现懒执行
      if(!options.lazy){
        effectFn()
      }
      return effectFn
    }

    function cleanup(effectFn){
      // 便利删除依赖
      for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
      }
      effectFn.deps.length = 0
    }
     
    function flushJob(){
      // 如果正在执行上一次收集的更新内容 则结束 
      if(isFlushing) return
      // 开始执行
      isFlushing = true
      // 使用 promise
      p.then(()=>{
        jobQueue.forEach(fn=>fn())
      }).finally(()=>{
        isFlushing = false
      })
    }

    function computed(getter){
      let ditry = true
      let value
      const effectFn = effect(getter, {
        lazy: true,
        scheduler(){
          if(!ditry){
            ditry = true
            trigger(obj, 'value')
          }
        }
      })
      const obj = {
        get value(){
          if(ditry){
            value = effectFn()
            ditry = false
          }
          track(obj, 'value')
          return value
        }
      }
      return obj
    }

    function watch(source, cb, options = {}){
      let getter
      
      if(typeof source === 'function'){
        getter = soucre
      } else {
        getter = () => traverse(source)
      }

      let oldVlaue, newValue
      const job = ()=>{
        // 在调度器中执行就是新值
        newValue = effectFn()
        // 将计算结果返回出去
        cb(newValue, oldVlaue)
        // 将新的计算结果保存到旧的当中
        oldVlaue = newValue
      }
      const effectFn = effect(() => getter(), {
        lazy: true,
        scheduler: ()=>{
          if(options.flush === 'post'){
            const p = Promise.resolve()
            p.then(()=>{job})
          } else {
            job()
          }
        }
      })

      if(options.immediate){
        job()
      } else {
        // 手动调用就是旧值
        oldVlaue = effectFn()
      }
      
    }

    function traverse(value, seen = new Set()){
      // 错误检测
      if(typeof value !== 'object' || value === null || seen.has(value))return 
      // 添加依赖
      seen.add(value)
      // 递归调用 深度监听
      for(const k in value){
        traverse(value[k], seen)
      }
    }

    watch(obj, ()=>{
      console.log('变化');
    })
    
    obj.a++

    // const sumRes = computed(()=>{
    //   return obj.a + obj.b
    // })
    // effect(()=>{
    //   console.log(sumRes.value);
    // })
    // obj.a++

    // const sumRes = computed(()=>{
    //   return obj.a + obj.b
    // })
    // console.log(sumRes);
    

    // const effectFn = effect(()=>{
    //   console.log(obj.name);
    // },{
    //   lazy: true
    // })

    // effectFn()

    // effect(()=>{
    //   console.log(obj.name);
    // },{
    //   scheduler(fn){
    //     setTimeout(fn,2000)
    //   }
    // })

    // effect(()=>{
    //   console.log(obj.name);
    // },{
    //   scheduler(fn){
    //     jobQueue.add(fn)
    //     flushJob()
    //   }
    // })

    // effect(function effectFn1(){
    //   console.log('effectFn1执行');
    //   effect(function effectFn2(){
    //     console.log('effectFn2执行');
    //     temp1 = obj.name;
    //   })
    //   tempe2 = obj.ok;
    // })

    // effect(()=>{
    //   document.body.innerHTML = obj.ok ? obj.name : 'no ok'
    // })
    obj.name = 'tom'
    
  </script>
</body>
</html>